package com.zf.rabbitmq.producer.publish_confirm;

import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 配置 发布确认回调 和 回退回调
 */
@Component
public class PublishConfirmCallbackConfig implements InitializingBean {
    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Override
    public void afterPropertiesSet() throws Exception {

        // 注意：交换机向队列发送消息时，如果找到了队列但是由于某些原因没有入队，可能进入死信队列，也能被丢弃，总之只要没有入队也会执行交换机的回调，算作发布到交换机失败
        // 设置发布确认的回调
        // 生产者向交换机发送消息后的回调，无论是否成功发送到交换机都会执行回调。
        // 想要回到生效，需要先在配置文件中配置 publisher-confirm-type: correlated 来开启到交换机的回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            /**
             * 向交换机发送消息后的回调，无论是否发送到交换机都会执行，可以对成功或者失败的消息做一些处理。
             * @param correlationData 发送消息时携带的关联数据，关联数据中可以存放消息的 标识和消息本体，没有携带就是 null，
             *                        见{@link com.zf.rabbitmq.producer.RabbitmqProducerApplicationTests#testPublishConfirmProducerToExchange()}
             * @param ack 发送到交换机成功（true）还是失败（false）
             * @param cause 失败的原因，成功是 null
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                // 发送到交换机成功
                if (ack) {
                    System.out.println("发送到交换机成功");
                } else {
                    // 发送到交换机失败
                    // 发送失败的消息的关联数据
                    String id = correlationData != null ? correlationData.getId() : "";
                    System.out.printf("id: %s 发送到交换机失败，原因：%s\n", id, cause);
                }
            }
        });

        // 设置交换机路由消息到队列失败的回调
        // 注意: 只有交换机找不到匹配队列才算是失败，也就是消息不可路由，才会执行该回退回调方法；一旦找到了队列 则不论消息最终是否进入队列 都不会执行回退回调，
        //     如果交换机找不到匹配队列，且配置了备份交换机，如果备份交换机找到了匹配队列则不会执行回退回调，如果备份交换机也没有找到匹配队列那么就会执行回退回调。
        // 设置消息回退的回调，要使该配置生效需要在配置文件中配置 publisher-returns=true 或者 rabbitTemplate.setMandatory(true)，
        // 如果都设置则以rabbitTemplate.setMandatory()为准。
        // 开启交换机发送消息到队列失败的回调，
        // rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {

            /**
             * 息传递过程中不可达目的地消息返回给生产者，只有找不到匹配队列才会执行该回退方法，其他情况都不执行
             * @param returnedMessage 回退消息
             *  - message 回退的消息
             *  - replyCode 回退原因的状态码
             *  - replyText 回退原因
             *  - exchange 哪个交换机向队列发送的消息
             *  - routingKey 发送消息时的路由键
             */
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                String msg = new String(returnedMessage.getMessage().getBody());
                System.out.printf("消息：[%s] 被发送到队列失败了，replyCode：%d，replyText：%s，exchange：%s，routingKey：%s\n",
                        msg, returnedMessage.getReplyCode(), returnedMessage.getReplyText(),
                        returnedMessage.getExchange(), returnedMessage.getRoutingKey());
            }
        });
    }
}
